// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Microsoft.TeamFoundation.Migration.Toolkit.Wit
{
    /// <summary>
    /// Field conflict analyzer; used by the analysis engine to detect field changes across
    /// revisions.
    /// </summary>
    class FieldConflictAnalyzer
    {
        private SyncProcess m_sharedData;                            // Shared data object
        private InternalWorkItemType m_type;                        // Type definition
        private Pair<FullId> m_ids;                                 // Ids of work items being compared
        private Dictionary<int, FieldRevisions> m_revisions;        // Revisions that must be accepted
        private Pair<int> m_conflictingRevision;                    // First conflicting revision

        /// <summary>
        /// Constructor for existing items with possible conflicts.
        /// </summary>
        /// <param name="type">Work item type</param>
        /// <param name="ids">Ids of work items</param>
        /// <param name="revisions">Revisions to analyze</param>
        /// <param name="mapper">Revisions mapper</param>
        public FieldConflictAnalyzer(
            InternalWorkItemType type,
            Pair<FullId> ids,
            Pair<List<MigrationRevision>> revisions,
            RevisionMapper mapper)
        {
            m_sharedData = type.SharedData;
            m_type = type;
            m_ids = ids;
            m_revisions = new Dictionary<int, FieldRevisions>();

            // Load changes
            Dictionary<int, List<FieldChange>> leftChanges = LoadChanges(Side.Left, revisions.Left, m_ids.Right==null);
            Dictionary<int, List<FieldChange>> rightChanges = LoadChanges(Side.Right, revisions.Right, m_ids.Left==null);

            foreach (KeyValuePair<int, List<FieldChange>> left in leftChanges)
            {
                List<FieldChange> rightValues;

                if (!rightChanges.TryGetValue(left.Key, out rightValues))
                {
                    rightValues = new List<FieldChange>();
                }
                Pair<List<FieldChange>> changes = new Pair<List<FieldChange>>(left.Value, rightValues);
                FieldRevisions revs = new FieldRevisions(m_sharedData, m_ids, changes, mapper);
                m_revisions.Add(left.Key, revs);
            }

            // Process remaining changes from the right
            foreach (KeyValuePair<int, List<FieldChange>> right in rightChanges)
            {
                if (!leftChanges.ContainsKey(right.Key))
                {
                    m_revisions.Add(
                        right.Key,
                        new FieldRevisions(m_sharedData, m_ids, new Pair<List<FieldChange>>(new List<FieldChange>(), right.Value), mapper));
                }
            }

            // Find out the first conflicting revision
            foreach (FieldRevisions revs in m_revisions.Values)
            {
                if (revs.ConflictingRevision != null)
                {
                    if (m_conflictingRevision == null)
                    {
                        m_conflictingRevision = revs.ConflictingRevision;
                    }
                    else
                    {
                        if (revs.ConflictingRevision.Left < m_conflictingRevision.Left)
                        {
                            m_conflictingRevision.Left = revs.ConflictingRevision.Left;
                        }
                        if (revs.ConflictingRevision.Right < m_conflictingRevision.Right)
                        {
                            m_conflictingRevision.Right = revs.ConflictingRevision.Right;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Returns the internal work item type.
        /// </summary>
        public InternalWorkItemType WorkItemType { get { return m_type; } }

        /// <summary>
        /// Gets first conflicting revision on both sides.
        /// </summary>
        public Pair<int> ConflictingRevision { get { return m_conflictingRevision; } }

        /// <summary>
        /// Checks whether the field was changed in the given revision on the given side and therefore
        /// must be copied into the source revision.
        /// </summary>
        /// <param name="side">Source side</param>
        /// <param name="revision">Revision on the source side</param>
        /// <param name="def">Field definition</param>
        /// <returns>True if the field must be taken</returns>
        public bool MustTakeField(
            Side sourceSide,
            int revision,
            InternalFieldDefinition def)
        {
            FieldRevisions revs;

            if (m_revisions.TryGetValue(def.Id, out revs))
            {
                return revs.SourceSide == sourceSide && revs.Revisions.BinarySearch(revision) >= 0;
            }
            return false;
        }

        /// <summary>
        /// Load changes from the given revisions.
        /// </summary>
        /// <param name="side">Side where the changes occurred</param>
        /// <param name="revisions">Revisions</param>
        /// <param name="isNew">Indicates if this work item is new (unmatched on the other side)</param>
        /// <returns>Changes map</returns>
        private Dictionary<int, List<FieldChange>> LoadChanges(
            Side side,
            List<MigrationRevision> revisions, 
            bool isNew)
        {
            Dictionary<int, List<FieldChange>> changes = new Dictionary<int, List<FieldChange>>();

            if (revisions.Count > 1)
            {
                Dictionary<int, object> baseline = new Dictionary<int, object>();

                for (int i = 0; i < revisions.Count; i++)
                {
                    MigrationRevision rev = revisions[i];

                    for (int j = 0; j < rev.Fields.Count; j++)
                    {
                        MigrationField f = rev.Fields[j];
                        InternalFieldDefinition fd = m_type.GetFieldDefinition(f.Name, side);

                        if (fd != null)
                        {
                            object oldValue;

                            baseline.TryGetValue(fd.Id, out oldValue);

                            //***Note: When it is a new work item (unmatched on the other side), 
                            //         we need to always include fields that have value maps 
                            //         because we need to be able to map empty values.

                            if (
                                (!m_sharedData.TfsValueComparer.Equals(oldValue, f.Value))
                                || ( (fd.ValueMap != null) && (isNew) ) //***
                                ) 
                            {
                                if (i > 0)
                                {
                                    List<FieldChange> fieldChanges;

                                    if (!changes.TryGetValue(fd.Id, out fieldChanges))
                                    {
                                        fieldChanges = new List<FieldChange>();
                                        changes.Add(fd.Id, fieldChanges);
                                    }
                                    fieldChanges.Add(new FieldChange(f.Name, rev.Revision, f.Value, fd));
                                }
                                baseline[fd.Id] = f.Value;
                            }
                        }
                    }
                }
            }
            return changes;
        }
    }
}
